package com.slipper.system.codeless.service;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

import com.slipper.constants.Constants;
import com.slipper.core.cache.CacheKey;
import com.slipper.core.cache.CacheUtil;
import com.slipper.core.message.MessageUtil;
import com.slipper.core.message.Topic;
import com.slipper.exception.CommonException;
import com.slipper.system.codeless.bo.*;
import com.slipper.system.codeless.dao.*;
import com.slipper.util.Tool;
import com.slipper.util.UUID;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;

import com.slipper.core.transaction.ReadTransactional;
import com.slipper.core.transaction.WriteTransactional;

@Service
@ReadTransactional
public class PageService {
    @Resource
    private PageDao pageDao;
    @Resource
    private DataTableDao dataTableDao;
    @Resource
    private DataFormDao dataFormDao;
    @Resource
    private DataModelDao dataModelDao;
    @Resource
    private ScriptGroovyDao scriptGroovyDao;
    @Resource
    private PageCompService pageCompService;
    @Resource
    private PageParamService pageParamService;
    @Resource
    private PageCompButtonService pageCompButtonService;
    @Resource
    private DataTableFieldService dataTableFieldService;
    
    public PageBO queryBy(String id) {
        if (Tool.isBlank(id)) {
            return null;
        }

        PageBO page = CacheUtil.get(CacheKey.Page.PREFIX + id, PageBO.class);
        if (page == null) {
            page = pageDao.queryBy(id);
            if (page == null) throw new CommonException("页面已被弃用！");
            page.setCompList(pageCompService.queryByPageId(id));
            page.setParamList(pageParamService.queryByPageId(id));
            CacheUtil.set(CacheKey.Page.PREFIX + id, page, 60 * 60);
        }
        return page;
    }
    
    public List<PageBO> queryList(PageBO bo) {
        return pageDao.queryList(bo);
    }
    
    public PageInfo<PageBO> queryPage(PageBO bo) {
        PageHelper.startPage(bo.getPageNumber(), bo.getPageSize());
        return new PageInfo<>(pageDao.queryList(bo));
    }
    
    @WriteTransactional
    public int save(PageBO bo) {
        int result = 0;
        if (Tool.isNotBlank(bo.getId())) {
            bo.setUpdateTime(System.currentTimeMillis());
            result += pageDao.update(bo);
        } else {
            bo.setId(UUID.getUUID());
            bo.setUpdateTime(System.currentTimeMillis());
            bo.setCreateTime(System.currentTimeMillis());
            result += pageDao.insert(bo);
        }

        if (!bo.getParamList().isEmpty()) {
            Map<String, String> oldIdMap = new HashMap<>();
            bo.getParamList().stream()
                    .filter(param -> param.getId().startsWith("temp_"))
                    .forEach(param -> {
                        String newId = UUID.getUUID();
                        oldIdMap.put(param.getId(), newId);
                        param.setId(newId);
                    });

            Set<String> nameSet = new HashSet<>();
            for (PageCompBO comp : bo.getCompList()) {
                if (Tool.isBlank(comp.getName())) throw new RuntimeException("组件名称不能为空！");
                if (!nameSet.add(comp.getName())) throw new RuntimeException("组件名称【" + comp.getName() + "】重复，请修改组件名称！");
                comp.getParamList().forEach(compParam -> {
                    if (compParam.getPageParamId().startsWith("temp_")) {
                        compParam.setPageParamId(oldIdMap.get(compParam.getPageParamId()));
                    }
                });

                comp.getButtonList().forEach(button -> {
                    if (Tool.isBlank(button.getPageFieldJson()) || !button.getPageFieldJson().contains("temp_")) return;

                    String json = button.getPageFieldJson();
                    Pattern pattern = Pattern.compile("(temp_\\d+)");
                    Matcher matcher = pattern.matcher(json);
                    while(matcher.find()) {
                        String tempId = matcher.group();
                        json = json.replace(matcher.group(), oldIdMap.get(tempId));
                    }
                    button.setPageFieldJson(json);
                });
            }
        }

        result += pageParamService.save(bo.getId(), bo.getParamList(), bo.getCreateId());
        result += pageCompService.save(bo.getId(), bo.getCompList(), bo.getCreateId());

        CacheUtil.remove(CacheKey.Page.PREFIX + bo.getId());
        MessageUtil.publish(Topic.PAGE_CHANGE);
        MessageUtil.publish(Topic.PAGE_COMP_CHANGE);

        return result;
    }

    @WriteTransactional
    public void saveList(List<PageBO> list) {
        if (list == null) return;

        list.forEach(item -> {
            if (pageDao.queryBy(item.getId()) != null) {
                item.setUpdateTime(System.currentTimeMillis());
                pageDao.update(item);
            } else {
                item.setUpdateTime(System.currentTimeMillis());
                item.setCreateTime(System.currentTimeMillis());
                pageDao.insert(item);
            }
            CacheUtil.remove(CacheKey.Page.PREFIX + item.getId());
        });
    }

    @WriteTransactional
    public int copy(PageBO bo) {
        if (Tool.isBlank(bo.getId())) {
            return 0;
        }

        PageBO newPage = pageDao.queryBy(bo.getId());
        if (newPage == null) {
            return 0;
        }

        newPage.setId(UUID.getUUID());
        newPage.setName(newPage.getName() + "_复制");
        newPage.setUpdateId(bo.getUpdateId());
        newPage.setCreateId(bo.getCreateId());
        newPage.setUpdateTime(System.currentTimeMillis());
        newPage.setCreateTime(System.currentTimeMillis());
        pageDao.insert(newPage);

        Map<String, String> paramMap = pageParamService.copyByPageId(bo.getId(), newPage.getId(), bo.getCreateId());
        pageCompService.copyByPageId(bo.getId(), newPage.getId(), bo.getCreateId(), paramMap);
        return 1;
    }

    @WriteTransactional
    public int updateFolder(PageBO bo) {
        bo.setUpdateTime(System.currentTimeMillis());
        int result = pageDao.updateFolder(bo);
        CacheUtil.remove(CacheKey.Page.PREFIX + bo.getId());
        return result;
    }
    
    @WriteTransactional
    public int delete(PageBO bo) {
        if (Tool.isBlank(bo.getId())) {
            return 0;
        }

        pageCompService.deleteByPageId(bo.getId());
        pageParamService.deleteByPageId(bo.getId());
        int result = pageDao.delete(bo);

        CacheUtil.remove(CacheKey.Page.PREFIX + bo.getId());
        return result;
    }

    public long queryTotal(PageBO bo) {
        PageHelper.startPage(0, 0);
        return new PageInfo<>(pageDao.queryList(bo)).getTotal();
    }

    public List<Map<String, String>> queryRelation(String pageId) {
        if (Tool.isBlank(pageId)) {
            return null;
        }

        List<Map<String, String>> nodeList = new ArrayList<>();
        this.createPageRelation(pageId, new HashMap<>(), nodeList);
        return nodeList;
    }

    private void createPageRelation(String pageId, Map<String, String> parent, List<Map<String, String>> nodeList) {
        if (Tool.isBlank(pageId)) return;

        // 避免循环
        if (nodeList.stream().anyMatch(item -> pageId.equals(item.get("id")) && "page".equals(item.get("type")))) {
            parent.put("pageId", pageId);
            return;
        }

        PageBO page = pageDao.queryBy(pageId);
        parent.put("pageId", page.getId());
        parent.put("pageName", page.getName());

        Map<String, String> pageNode = new HashMap<>();
        pageNode.put("id", page.getId());
        pageNode.put("type", "page");
        pageNode.put("name", page.getName());
        pageNode.put("childrenIds", "");
        addNode(pageNode, nodeList);

        pageCompService.queryByPageId(page.getId()).forEach(comp -> this.createCompNode(comp, pageNode, nodeList));
    }

    private void createCompNode(PageCompBO comp, Map<String, String> parent, List<Map<String, String>> nodeList) {
        parent.put("childrenIds", parent.get("childrenIds") + "," + comp.getId());

        Map<String, String> compNode = new HashMap<>();
        compNode.put("id", comp.getId());
        compNode.put("type", "comp");
        compNode.put("compType", comp.getType());
        compNode.put("name", comp.getName());
        addNode(compNode, nodeList);

        if (PageCompBO.TYPE_TABLE.equals(comp.getType())) {
            if (Constants.YES.equals(comp.getShowTree()) && Tool.isNotBlank(comp.getTreeDataModelId())) {
                compNode.put("treeId", comp.getTreeDataModelId());
                compNode.put("treeName", "左侧树");
                createModelNode(comp.getTreeDataModelId(), nodeList);
            }

            this.createTableNode(comp.getDataTableId(), compNode, nodeList);
            this.createFormNode(comp.getDataFormId(), compNode, nodeList);
        } else if (PageCompBO.TYPE_FORM.equals(comp.getType())) {
            this.createFormNode(comp.getDataFormId(), compNode, nodeList);
        } else if (PageCompBO.TYPE_PAGE.equals(comp.getType())) {
            this.createPageRelation(comp.getDataPageId(), compNode, nodeList);
        }

        compNode.put("childrenIds", "");
        pageCompButtonService.queryByCompId(comp.getId())
                .stream()
                .filter(button -> Tool.isBlank(button.getButtonId()))
                .forEach(button -> this.createButtonNode(button, compNode, nodeList));
    }

    private void createModelNode(String modelId, List<Map<String, String>> nodeList) {
        if (Tool.isBlank(modelId)) {
            return;
        }

        if (nodeList.stream().noneMatch(item -> modelId.equals(item.get("id"))
                && "model".equals(item.get("type")))) {
            DataModelBO dataModel = dataModelDao.queryBy(modelId);
            if (dataModel != null) {
                Map<String, String> node = new HashMap<>();
                node.put("id", dataModel.getId());
                node.put("type", "model");
                node.put("name", dataModel.getName());
                addNode(node, nodeList);

                this.createScriptNode(dataModel.getBeforeInsertId(), node, nodeList);
                this.createScriptNode(dataModel.getBeforeUpdateId(), node, nodeList);
                this.createScriptNode(dataModel.getBeforeDeleteId(), node, nodeList);
                this.createScriptNode(dataModel.getAfterInsertId(), node, nodeList);
                this.createScriptNode(dataModel.getAfterUpdateId(), node, nodeList);
                this.createScriptNode(dataModel.getAfterDeleteId(), node, nodeList);
            }
        }
    }

    private void createTableNode(String dataTableId, Map<String, String> parent, List<Map<String, String>> nodeList) {
        if (Tool.isBlank(dataTableId)) return;

        DataTableBO dataTable = dataTableDao.queryBy(dataTableId);
        if (dataTable == null) return;

        Map<String, String> node = new HashMap<>();
        node.put("id", dataTable.getId());
        node.put("type", "table");
        node.put("name", dataTable.getName());
        node.put("modelId", dataTable.getModelId());
        addNode(node, nodeList);
        createModelNode(dataTable.getModelId(), nodeList);
        createScriptNode(dataTable.getTipsScriptId(), node, nodeList);

        parent.put("tableId", dataTable.getId());
        parent.put("tableName", dataTable.getName());

        List<DataTableFieldBO> dataTableField = dataTableFieldService.queryByTableId(dataTable.getId());
        dataTableField.forEach(field -> {
            createPageRelation(field.getPageId(), node, nodeList);
            createScriptNode(field.getOpenScriptId(), node, nodeList);
            createScriptNode(field.getCloseScriptId(), node, nodeList);
        });
    }

    private void createFormNode(String dataFormId, Map<String, String> parent, List<Map<String, String>> nodeList) {
        if (Tool.isBlank(dataFormId)) return;

        DataFormBO dataForm = dataFormDao.queryBy(dataFormId);
        if (dataForm == null) return;

        Map<String, String> node = new HashMap<>();
        node.put("id", dataForm.getId());
        node.put("type", "form");
        node.put("name", dataForm.getName());
        node.put("modelId", dataForm.getModelId());
        node.put("childrenIds", "");
        addNode(node, nodeList);
        createModelNode(dataForm.getModelId(), nodeList);
        createScriptNode(dataForm.getInitScriptId(), node, nodeList);
        createScriptNode(dataForm.getTipsScriptId(), node, nodeList);

        parent.put("formId", dataForm.getId());
        parent.put("formName", dataForm.getName());
    }

    private void createScriptNode(String scriptId, Map<String, String> parent, List<Map<String, String>> nodeList) {
        if (Tool.isBlank(scriptId)) return;

        ScriptGroovyBO scriptGroovy = scriptGroovyDao.queryBy(scriptId);
        if (scriptGroovy == null) return;

        parent.put("childrenIds", parent.get("childrenIds") + "," + scriptGroovy.getId());
        Map<String, String> node = new HashMap<>();
        node.put("id", scriptGroovy.getId());
        node.put("type", "script");
        node.put("name", scriptGroovy.getName());
        addNode(node, nodeList);

        parent.put("scriptId", scriptGroovy.getId());
        parent.put("scriptName", scriptGroovy.getName());
    }

    private void createButtonNode(
            PageCompButtonBO button,
            Map<String, String> parent,
            List<Map<String, String>> nodeList) {
        parent.put("childrenIds", parent.get("childrenIds") + "," + button.getId());

        Map<String, String> buttonNode = new HashMap<>();
        buttonNode.put("id", button.getId());
        buttonNode.put("type", "button");
        buttonNode.put("name", button.getName());
        addNode(buttonNode, nodeList);

        this.createPageRelation(button.getDialogPageId(), buttonNode, nodeList);
        this.createFormNode(button.getDialogFormId(), buttonNode, nodeList);
        this.createScriptNode(button.getExecuteScriptId(), buttonNode, nodeList);
    }

    private void addNode(Map<String, String> node, List<Map<String, String>> nodeList) {
        if (nodeList.stream().noneMatch(item -> node.get("id").equals(item.get("id"))
                && node.get("type").equals(item.get("type")))) {
            nodeList.add(node);
        }
    }

    public List<Map<String, Integer>> queryTypeCount() {
        return pageDao.queryTypeCount();
    }
}
